#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import logging
import json

import pika
import tornado

from pika import adapters


class RPCClient(object):
    EXCHANGE_TYPE = 'direct'
    EXCHANGE_TPL = '{}.rpc.exchange'
    ROUTINGKEY_TPL = '{}-rpc-queue'

    def __init__(self, name, amqp_url):
        self._name = name
        self._url = amqp_url
        self._exchange = self.EXCHANGE_TPL.format(self._name)
        self._routing_key = self.ROUTINGKEY_TPL.format(self._name)

        self._conn = None
        self._chann = None

        self._deliveries = None
        self._acked = None
        self._nacked = None
        self._message_number = None

        self._stopping = False


    def connect(self):
        return adapters.TornadoConnection(pika.URLParameters(self._url),
                                          on_open_callback=self.on_connection_open,
                                          on_close_callback=self.on_connection_closed,
                                          stop_ioloop_on_close=False)

    def on_connection_open(self, unused_connection):
        self.open_channel()

    def on_connection_closed(self, connection, reply_code, reply_text):
        self._chann = None
        if self._stopping:
            self._conn.ioloop.stop()
        else:
            self._conn.add_timeout(5, self._conn.ioloop.stop)

    def open_channel(self):
        self._conn.channel(on_open_callback=self.on_channel_open)

    def on_channel_open(self, channel):
        self._chann = channel
        self.add_on_channel_close_callback()
        self.enable_delivery_confirmations()
        self.setup_exchange(self._exchange)
    
    def add_on_channel_close_callback(self):
        self._chann.add_on_close_callback(self.on_channel_closed)

    def on_channel_closed(self, channel, reply_code, reply_text):
        self._chann = None
        if not self._stopping:
            self._conn.close()

    def setup_exchange(self, exchange_name):
        self._chann.exchange_declare(self.on_exchange_declareok, exchange_name, self.EXCHANGE_TYPE)

    def on_exchange_declareok(self, unused_frame):
        pass

    def setup_queue(self, on_queue_declareok):
        self._chann.queue_declare(on_queue_declareok, exclusive=True)

    def bind_queue(self, queue_name, on_bindok_callback):
        self._chann.queue_bind(on_bindok_callback, queue=queue_name, exchange=self._exchange)

    def setup_consuming(self, queue_name, on_rpc_response):
        self._chann.basic_consume(on_rpc_response, no_ack=True,
                                   queue=queue_name)
    # def on_queue_declareok(self, queue_name):
    #     self._chann.queue_bind(self.on_bindok, queue_name, self.EXCHANGE, self._routing_key)

    def on_bindok(self, unused_frame):
        raise NotImplementedError()

    def enable_delivery_confirmations(self):
        self._chann.confirm_delivery(self.on_delivery_confirmation)

    def on_delivery_confirmation(self, method_frame):
        confirmation_type = method_frame.method.NAME.split('.')[1].lower()
        if confirmation_type == 'ack':
            self._acked += 1
        elif confirmation_type == 'nack':
            self._nacked += 1
        self._deliveries.remove(method_frame.method.delivery_tag)


    def publish_message(self, message, callback_queue, corr_id):
        if self._chann is None or not self._chann.is_open:
            return

        self._chann.basic_publish(exchange=self._exchange,
                                routing_key=self._routing_key,
                                properties=pika.BasicProperties(
                                    reply_to=callback_queue,
                                    correlation_id=corr_id,
                                ),
                                body=message)

        self._message_number += 1
        self._deliveries.append(self._message_number)

    def init(self):
        self._conn = None
        self._deliveries = []
        self._acked = 0
        self._nacked = 0
        self._message_number = 0

        self._conn = self.connect()

    def stop(self):
        self._stopping = True
        self.close_channel()
        self.close_connnection()

    def status(self):
        data = {
            'message': self._message_number,
            'acked': self._acked,
            'noacked': self._nacked,
        }

        return data

    def close_channel(self):
        if self._chann is not None:
            self._chann.close()
    
    def close_connnection(self):
        if self._conn is not None:
            self._conn.close()
